﻿//-----------------------------------------------------------------------
// <copyright file="StatusManager.cs" company="Microsoft Corporation">
//     Copyright Microsoft Corporation. All Rights Reserved. This code released under the terms of the Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.) This is sample code only, do not use in production environments.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.ALMRangers.VsarTreasureMap.WindowsStoreApp.Models
{
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Runtime.Serialization.Json;

    /// <summary>
    /// Defines the class which provides functions for managing the status of objects.
    /// </summary>
    public class StatusManager
    {
        /// <summary>
        /// The database
        /// </summary>
        private readonly DB database;

        /// <summary>
        /// The saved status
        /// </summary>
        private List<GuidanceStatus> savedStatus;

        /// <summary>
        /// Initializes a new instance of the <see cref="StatusManager" /> class.
        /// </summary>
        /// <param name="databaseManager">The database Manager.</param>
        public StatusManager(DB databaseManager)
        {
            this.database = databaseManager;

            this.Load();
        }

        /// <summary>
        /// Gets the total count of guidance documents that have been marked as
        /// Completed.
        /// </summary>
        /// <value>The completed guidance count.</value>
        public int CompletedGuidanceCount { get; private set; }

        /// <summary>
        /// Gets the count of projects where all guidance documents have been marked
        /// as Completed.
        /// </summary>
        /// <value>The completed project count.</value>
        public int CompletedProjectCount { get; private set; }

        /// <summary>
        /// Gets the total count of projects.
        /// </summary>
        /// <value>The project count.</value>
        public int ProjectCount { get; private set; }

        /// <summary>
        /// Gets the total count of guidance documents, for a given project, that
        /// have been marked as Completed.
        /// </summary>
        /// <param name="projectId">The ID of the project to return a completed guidance
        /// count for.</param>
        /// <returns>The total count of guidance documents, for a given project, that
        /// have been marked as Completed.</returns>
        public int GetCompletedDocumentCount(int projectId)
        {

            return (from category in this.database.Categories
                    from project in category.Projects
                    where project.Id == projectId
                    from guidance in project.Guidance
                    where guidance.Status == Status.Completed
                    select guidance).Count();
        }

        /// <summary>
        /// Gets the total count of projects, for a given category, where all
        /// guidance documents have been marked as Completed.
        /// </summary>
        /// <param name="categoryId">The ID of the category to return a completed guidance
        /// count for.</param>
        /// <returns>The total count of projects, for a given category, where all
        /// guidance documents have been marked as Completed.</returns>
        public int GetCompletedProjectCount(int categoryId)
        {
            var completedProjects = 0;

            var category = this.database.Categories.FirstOrDefault(x => x.Id == categoryId);

            if (category != null)
            {
                foreach (var project in category.Projects)
                {
                    // Check to see if the completed Guidance count matches the entire Guidance count
                    if (project.Guidance.Where(x => x.Status == Status.Completed).Count() == project.Guidance.Count())
                    {
                        completedProjects++;
                    }
                }
            }

            return completedProjects;
        }

        /// <summary>
        /// Gets the project count.
        /// </summary>
        /// <param name="category">The category.</param>
        /// <returns>System.Int32.</returns>
        public int GetProjectCount(int category)
        {
            return (from c in App.Database.Categories
                    where c.Id == category
                    select c).Single().Projects.Length;
        }

        /// <summary>
        /// Gets the total count of guidance documents for a given project.
        /// </summary>
        /// <param name="projectId">The ID of the project to return a guidance count for.</param>
        /// <returns>The total count of guidance documents for a given project.</returns>
        public int GetProjectDocumentCount(int projectId)
        {
            var documentCount = 0;

            foreach (var category in this.database.Categories)
            {
                var project = category.Projects.FirstOrDefault(x => x.Id == projectId);

                if (project != null)
                {
                    documentCount = project.Guidance.Length;

                    break;
                }
            }

            return documentCount;
        }

        /// <summary>
        /// Sets the status for a specific guidance document.
        /// </summary>
        /// <param name="guidanceId">The ID of the guidance document to be updated.</param>
        /// <param name="status">The guidance document status.</param>
        public void SetStatus(int guidanceId, Status status)
        {
            var isInList = false;
            GuidanceStatus listItem = null;

            // Locate the item in the list of saved status, if it exists
            foreach (var item in this.savedStatus)
            {
                if (item.Id == guidanceId)
                {
                    isInList = true;
                    listItem = item;

                    break;
                }
            }

            if (status == Status.NotStarted)
            {
                // Remove the item from the list if it is present (i.e. no need to persist items not yet started)
                this.savedStatus.Remove(listItem);
            }
            else
            {
                if (isInList)
                {
                    // Update existing status
                    listItem.Status = status;
                }
                else
                {
                    // Add status to list
                    this.savedStatus.Add(new GuidanceStatus(guidanceId, status));
                }
            }

            this.UpdateGuidanceStatus(guidanceId, status);
            this.UpdateStatistics();

            // Save the current list
            this.Save();
        }

        /// <summary>
        /// Loads the list of guidance status from roaming storage.
        /// </summary>
        private void Load()
        {
            if (Windows.Storage.ApplicationData.Current.RoamingSettings.Values.ContainsKey("SavedStatus"))
            {
                // Deserialize the JSON text
                var jsonText = Windows.Storage.ApplicationData.Current.RoamingSettings.Values["SavedStatus"].ToString();
                using (var memStream = new MemoryStream(System.Text.UnicodeEncoding.Unicode.GetBytes(jsonText)))
                {
                    var jsonSer = new DataContractJsonSerializer(typeof(List<GuidanceStatus>));

                    memStream.Position = 0;

                    this.savedStatus = (List<GuidanceStatus>)jsonSer.ReadObject(memStream);
                }
            }
            else
            {
                this.savedStatus = new List<GuidanceStatus>();
            }

            // Update the Category/Project/Guidance object hierarchy with the appropriate status values
            // for each of the persisted Guidance items
            foreach (var item in this.savedStatus)
            {
                this.UpdateGuidanceStatus(item.Id, item.Status);
            }

            this.UpdateStatistics();
        }

        /// <summary>
        /// Saves the list of guidance statuses to roaming storage.
        /// </summary>
        private void Save()
        {
            // Serialize the status list as JSON text
            var memStream = new MemoryStream(); // this doesn't need a using as the reader will dispose of it

            var jsonSer = new DataContractJsonSerializer(typeof(List<GuidanceStatus>));

            jsonSer.WriteObject(memStream, this.savedStatus);
            memStream.Position = 0;

            using (var reader = new StreamReader(memStream))
            {
                Windows.Storage.ApplicationData.Current.RoamingSettings.Values["SavedStatus"] = reader.ReadToEnd();
            }
        }

        /// <summary>
        /// Sets the completed guidance count.
        /// </summary>
        private void SetCompletedGuidanceCount()
        {
            this.CompletedGuidanceCount = this.savedStatus.Where(x => x.Status == Status.Completed).Count();
        }

        /// <summary>
        /// Sets the completed project count.
        /// </summary>
        private void SetCompletedProjectCount()
        {
            var completedProjects = 0;

            foreach (var category in this.database.Categories)
            {
                foreach (var project in category.Projects)
                {
                    // Check to see if the completed Guidance count matches the entire Guidance count
                    if (project.Guidance.Where(x => x.Status == Status.Completed).Count() == project.Guidance.Count())
                    {
                        completedProjects++;
                    }
                }
            }

            this.CompletedProjectCount = completedProjects;
        }

        /// <summary>
        /// Sets the project count.
        /// </summary>
        private void SetProjectCount()
        {
            var projectCount = 0;

            foreach (var category in this.database.Categories)
            {
                projectCount += category.Projects.Count();
            }

            this.ProjectCount = projectCount;
        }

        /// <summary>
        /// Updates the guidance status for a specific Guidance ID.
        /// </summary>
        /// <param name="guidanceId">The ID of the guidance item to be updated.</param>
        /// <param name="status">The guidance status.</param>
        private void UpdateGuidanceStatus(int guidanceId, Status status)
        {
            Guidance guidance = null;

            // Locate specific guidance item in Project/Category/Guidance hierarchy
            foreach (var category in this.database.Categories)
            {
                foreach (var project in category.Projects)
                {
                    foreach (var guidanceItem in project.Guidance)
                    {
                        if (guidanceItem.Id == guidanceId)
                        {
                            guidance = guidanceItem;
                            break;
                        }
                    }

                    if (guidance != null)
                    {
                        break;
                    }
                }

                if (guidance != null)
                {
                    break;
                }
            }

            // Update the Guidance status
            if (guidance != null)
            {
                guidance.Status = status;
            }
        }

        /// <summary>
        /// Updates the statistics exposed by the public properties of this instance.
        /// </summary>
        private void UpdateStatistics()
        {
            this.SetCompletedProjectCount();
            this.SetCompletedGuidanceCount();
            this.SetProjectCount();
        }
    }
}